# -*- coding: utf-8 -*-
# @Time        : 2022/12/30 10:12
# @File        : edu_admin.py
# @Description : None
# ----------------------------------------------
# ☆ ☆ ☆ ☆ ☆ ☆ ☆
# >>> Author    : Alex
# >>> Mail      : liu_zhao_feng_alex@163.com
# >>> Github    : https://github.com/koking0
# >>> Blog      : https://alex007.blog.csdn.net/
# ☆ ☆ ☆ ☆ ☆ ☆ ☆
import time
import copy
import hashlib
import json
import math
import os
import re
import time
import logging
import datetime
from urllib import parse

import requests

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException

CUR_SEMESTER = "2024-2025-1-2"  # 当前学期编码
NXT_SEMESTER = "2025-2026-1-1"  # 下学期编码
# 每节课的开始时间
course_start_time_dict = {
    1: "08:20",
    2: "09:15",
    3: "10:20",
    4: "11:15",
    5: "13:30",
    6: "14:25",
    7: "15:25",
    8: "16:20",
    9: "17:15",
    10: "18:30",
    11: "19:25",
    12: "20:25",
    13: "21:20",
}

cur_file_path = os.path.dirname(__file__)
schedule_file_dir = os.path.join(cur_file_path, "data")
tmp_file_dir = os.path.join(cur_file_path, "tmp")
account_file = os.path.join(schedule_file_dir, "account.json")
if not os.path.exists(schedule_file_dir):
    os.mkdir(schedule_file_dir)
if not os.path.exists(tmp_file_dir):
    os.mkdir(tmp_file_dir)
# 如果账户信息文件不存在，则创建
if not os.path.exists(account_file):
    with open(account_file, 'w') as f:
        json.dump({}, f)

logger = logging.getLogger("pioneer_xiaoke")


def get_today_week_day():
    # 爬学校教务系统的教学周，代码巨狗，不建议细看
    jw_url = "http://jw.tust.edu.cn/"
    header = {"user-agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)"}
    page_html = requests.get(url=jw_url, headers=header, timeout=500).text
    # date1 = time.strptime(re.findall(r'var date1 = new Date\("(.+?)"\);', page_html)[0], "%Y/%m/%d")
    date1 = time.strptime("2025/09/08", "%Y/%m/%d")
    date2 = time.localtime()
    date3 = ((time.mktime(date2) * 1000 - time.mktime(date1) * 1000) / (1000 * 60 * 60 * 24)) / 7
    current_count = math.ceil(date3)
    # 获取今天的星期数，星期天 week_day = 0
    week_day = time.strftime("%w", time.localtime())
    if int(week_day) == 0:  # 如果今天是周末，则明天为下一个教学周的周一
        week_day = 7
    logging.info(f"获取教务系统的教学周：{current_count}，获取今天的星期数：{week_day}")
    return int(current_count), int(week_day)
    # return 3, int(week_day)


def extract_weeks(input_str):
    """
    从字符串中提取出周数的数字列表。

    Args:
        input_str (str): 输入的字符串。

    Returns:
        list: 包含提取出的周数的数字列表。
    """
    week_pattern = re.compile(r'(\d+(?:-\d+)?(?:,\d+)*)周')
    match = week_pattern.search(input_str)

    if not match:
        return []

    week_str = match.group(1)
    week_list = []

    for part in week_str.split(','):
        if '-' in part:
            start, end = map(int, part.split('-'))
            week_list.extend(range(start, end + 1))
        else:
            week_list.append(int(part))

    return week_list


def extract_weekday(input_str):
    """
    从字符串中提取星期几并转换为对应的数字。
    星期一对应 1，星期二对应 2，...，星期日对应 7。

    参数:
        input_str (str): 包含星期几的字符串。

    返回:
        int: 对应的星期几数字，如果未匹配到，则返回 None。
    """
    weekdays = {
        "星期一": 1,
        "星期二": 2,
        "星期三": 3,
        "星期四": 4,
        "星期五": 5,
        "星期六": 6,
        "星期日": 7
    }

    for day, num in weekdays.items():
        if day in input_str:
            return num
    return None


def extract_periods(input_str):
    """
    从字符串中提取课程的节数，并转换为数字列表。

    参数:
        input_str (str): 包含课程节数的字符串。

    返回:
        list: 包含课程节数的数字列表。
    """
    # 使用正则表达式提取 `X-Y节` 的部分
    match = re.search(r"(\d+)-(\d+)节", input_str)
    if match:
        start, end = int(match.group(1)), int(match.group(2))
        return [start, end]
    return []


class SuperEagleClient:
    """ 超级鹰验证码识别 """

    def __init__(self, username, password, soft_id):
        self.username = username
        self.password = hashlib.md5(password.encode("utf8")).hexdigest()
        self.soft_id = soft_id
        self.base_params = {"user": self.username, "pass2": self.password, "softid": self.soft_id}
        self.headers = {
            "Connection": "Keep-Alive",
            "User-Agent": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)",
        }

    def post_pic(self, im, codetype):
        """
        im: 图片字节
        codetype: 题目类型 参考 http://www.chaojiying.com/price.html
        """
        params = {"codetype": codetype}
        params.update(self.base_params)
        files = {"userfile": ("ccc.jpg", im)}
        upload_url = "http://upload.chaojiying.net/Upload/Processing.php"
        r = requests.post(upload_url, data=params, files=files, headers=self.headers)
        return r.json()


class EducationAdminister:
    """ 教务系统管理 """

    def __init__(self, student_number, student_password, course_json=None):
        self.student_number = student_number
        self.student_password = student_password
        self.course_json = course_json

    def __login(self, retry=3):
        """ 登录教务系统 """
        if retry < 1:
            return "登录失败，请联系系统管理员", False

        # 创建当前用户的临时文件夹
        user_tmp_dir = os.path.join(tmp_file_dir, self.student_number)
        if not os.path.exists(user_tmp_dir):
            os.mkdir(user_tmp_dir)
        # 获取登录页面，设置Cookie
        login_page_url = "http://jwxtxs.tust.edu.cn:46110/login"
        session = requests.session()
        login_page = session.get(login_page_url)
        # 将登录页面返回内容写入文件，方便debug
        with open(os.path.join(user_tmp_dir, "login_page.html"), "wb") as fp:
            fp.write(login_page.content)
        # 通过正则表达式提取 token
        token_value = re.findall(r'id="tokenValue" name="tokenValue" value="(.+?)">', login_page.text)[0]
        # 获取验证码并识别
        captcha = session.get("http://jwxtxs.tust.edu.cn:46110/img/captcha.jpg")
        super_eagle = SuperEagleClient("Alex007", "Alex007.", "938831")
        pic_str = super_eagle.post_pic(captcha.content, 1902).get("pic_str", None)
        # 输入账号、密码、验证码，登录
        login_url = "http://jwxtxs.tust.edu.cn:46110/j_spring_security_check"
        login_data = {
            "j_username": self.student_number, "j_password": self.md5_string(self.student_password),
            "j_captcha": pic_str, "tokenValue": token_value
        }
        logging.info(f"login_data: {login_data}")
        login_res = session.post(login_url, data=login_data, allow_redirects=False)
        # 将登录请求返回内容写入文件，方便debug
        with open(os.path.join(user_tmp_dir, "login_res.html"), "wb") as fp:
            fp.write(login_res.content)

        if "badCredentials" in str(login_res.content):
            return f"用户：{self.student_number} 登录失败，账号或密码错误！", False
        elif "badCaptcha" in str(login_res.content):
            return self.__login(retry=retry - 1)
        elif login_res.status_code == 302:  # 登录成功后会重定向
            logging.info(f"用户：{self.student_number} 登录成功")
            return session, True
        return session, False

    def get_user_course(self):
        """ 模拟登录教务系统，获取用户本学期课表，并保存课表信息 """
        sess, success = self.__login()  # 用户登录
        if success:  # 如果登录成功
            url = "http://jwxtxs.tust.edu.cn:46110/student/courseSelect/thisSemesterCurriculum/index"
            response = sess.post(url)
            try:
                # 通过正则表达式获取请求课表中拼接的随机参数
                url_template = "/student/courseSelect/thisSemesterCurriculum/{code}/ajaxStudentSchedule/curr/callback"
                code = re.findall(url_template.format(code="(.+?)"), response.text)[0]
                course_request_url = "http://jwxtxs.tust.edu.cn:46110" + url_template.format(code=code)
                course_response = sess.post(course_request_url).json()
                return course_response
            except Exception as e:
                # 如果请求失败，打印错误信息，保存请求下来的文件
                logging.error(f"get_course error: {e}")
                return "获取课表失败，请联系管理员 vx：18812649207。"
        elif type(sess) == str:
            return sess
        else:
            return "登录竟然失败了，若确保账号或密码无误，请联系管理员QQ：2426671397。"

    def get_user_gpa(self):
        """ 模拟登录教务系统，获取用户绩点信息 """
        sess, success = self.__login()  # 用户登录
        if success:  # 如果登录成功
            res = sess.post("http://jwxtxs.tust.edu.cn:46110/main/academicInfo").json()[0]
            # 组织学业信息的消息
            academic_msg = f"学号：{self.student_number} 学业信息\n" \
                           f"已修读课程门数：{res['courseNum']}\n尚不及格课程门数：{res['coursePas']}\n" \
                           f"本学期待修读课程门数：{res['courseNum_bxqyxd']}\n绩点成绩：{res['gpa']}"
            return academic_msg
        elif type(sess) == str:
            return sess
        else:
            return "登录竟然失败了，若确保账号或密码无误，请联系管理员QQ：2426671397。"

    def get_user_score(self):
        """ 模拟登录教务系统，获取用户成绩信息 """
        sess, success = self.__login()  # 用户登录
        if success:  # 如果登录成功
            url = "http://jwxtxs.tust.edu.cn:46110/student/integratedQuery/scoreQuery/schemeScores/index"
            response = sess.get(url)
            # 获取请求课表中拼接的随机参数
            url_template = "/student/integratedQuery/scoreQuery/{code}/schemeScores/callback"
            codes = re.findall(url_template.format(code="(.+?)"), response.text)
            scheme_score_url = "http://jwxtxs.tust.edu.cn:46110" + url_template.format(code=codes[0])
            # 请求方案成绩信息
            scheme_score_res = sess.get(scheme_score_url).json()["lnList"][0]
            # 组织用户方案成绩消息
            scheme_score_msg = f"用户：{self.student_number} " + scheme_score_res["cjlx"]
            for course in scheme_score_res["cjList"]:
                scheme_score_msg += f"\n\n课程名：{course['courseName']}\n" \
                                    f"学分：{course['credit']}   成绩：{course['cj']}"
            return scheme_score_msg
        elif type(sess) == str:
            return sess
        else:
            return "登录竟然失败了，若确保账号或密码无误，请联系管理员QQ：2426671397。"

    def get_next_semester_course(self):
        """ 模拟登录教务系统，获取用户下学期课表 """
        sess, success = self.__login()  # 用户登录
        if success:  # 如果登录成功
            url = "http://jwxtxs.tust.edu.cn:46110/student/courseSelect/calendarSemesterCurriculum/index"
            response = sess.get(url)
            try:
                # 通过正则表达式获取请求课表中拼接的随机参数
                url_template = "/student/courseSelect/thisSemesterCurriculum/{code}/ajaxStudentSchedule/past/callback"
                code = re.findall(url_template.format(code="(.+?)"), response.text)[0]
                course_request_url = "http://jwxtxs.tust.edu.cn:46110" + url_template.format(code=code)
                # 发送获取课表的请求，携带下学期的编码参数，注意是发送application/x-www-form-urlencoded类型的数据
                headers = {"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
                payload = parse.urlencode({"planCode": NXT_SEMESTER})
                course_response = sess.post(course_request_url, headers=headers, data=payload).json()
                # 学期编码 2022-2023-1-1 -> 2022-2023学年秋
                semester_course_msg = NXT_SEMESTER[:9] + "学年" + ("秋" if NXT_SEMESTER[10] == '1' else "春") + "\n"
                for course in course_response["xkxx"][0].values():  # 遍历课程列表
                    semester_course_msg += f"\n课程名：{course['courseName']}\n学分：{course['unit']}" \
                                           f"\n课程属性：{course['coursePropertiesName']}" \
                                           f"\n课程类别：{course['courseCategoryName']}"
                    if course["attendClassTeacher"]:  # 如果课程有老师，拼接教师信息
                        semester_course_msg += f"\n教师：{course['attendClassTeacher']}"
                    if course["timeAndPlaceList"]:  # 如果是线下课，拼接教室信息
                        semester_course_msg += "\n-------\n"
                        for time_place in course["timeAndPlaceList"]:
                            semester_course_msg += f"{time_place['weekDescription']} 星期{time_place['classDay']} " \
                                                   f"第{time_place['classSessions']}节 " \
                                                   f"{time_place['campusName']}：" \
                                                   f"{time_place['teachingBuildingName']}-" \
                                                   f"{time_place['classroomName']}\n"
                        semester_course_msg += "-------"
                    semester_course_msg += "\n"
                logging.info(f"semester_course_msg: {semester_course_msg}")
                return semester_course_msg
            except Exception as e:
                logging.error(f"get_course error: {e}")
                return "获取课表失败，请联系管理员QQ：2426671397。"
        elif type(sess) == str:
            return sess
        else:
            return "登录竟然失败了，若确保账号或密码无误，请联系管理员QQ：2426671397。"

    @staticmethod
    def get_day_course_list(course_json, w, d):
        today_course_list = []
        for course_detail in course_json["xkxx"][0].values():  # 遍历课程列表
            for time_and_place in course_detail["timeAndPlaceList"]:
                # "classWeek": "111111111111000000000000"，其中1表示上课周，0表示本周没有这门课
                class_week = time_and_place["classWeek"]
                if class_week[w - 1] == '1' and d == time_and_place["classDay"]:
                    # 获取上课时间
                    course_time = time_and_place["classSessions"]
                    # 拼接上课地点
                    campus, build = time_and_place["campusName"], time_and_place["teachingBuildingName"]
                    classroom = time_and_place["classroomName"]
                    # 整理课程信息
                    today_course_list.append({
                        "课程名": time_and_place["coureName"],
                        "教师": course_detail["attendClassTeacher"],
                        "时间": course_time,
                        "地点": ">>".join([campus, build, classroom]),
                    })
        return today_course_list

    def get_flag_day_course_msg(self, flag, target_weekday=0) -> str:
        """
        获取日课表信息
        Args:
            flag: 1-获取今天课表，2-获取明天课表，3-获取后天课表

        Returns: 课表信息字符串
        """

        def get_day_course_msg(day: str, w: int, d: int):
            if w > 20:
                return f"现在是第 {w} 周，还没放假？还看课表？还想上课？"
            logging.info(f"获取第 {w} 周星期 {d} 课表信息")
            if self.course_json:
                # 获取用户课表中对应周数和星期数课程列表
                today_course_list = self.get_day_course_list(self.course_json, w, d)
                if len(today_course_list) > 0:
                    today_course_list.sort(key=lambda x: x["时间"])
                    msg = f"{day} 日是第 {w} 教学周，星期 {d}。\n一共有 {len(today_course_list)} 节课哦~"
                    for idx, course in enumerate(today_course_list):
                        msg += f"\n\n第 {idx + 1} 节：{course['课程名']}"
                        msg += f"\n老师：{course['教师'].replace('*', '')}"
                        msg += f"\n时间：{self.get_course_start_end_time_string(int(course['时间']))}"
                        # 泰达>>9 为中院九号楼，西院的同学很多都不知道，因此如果出现的话单独加个备注
                        if "泰达>>9" in course["地点"]:
                            msg += f"\n地点：{course['地点']}\n（泰达>>9 为中院九号楼，即中院图书馆！）"
                        elif "物理实验" in course["课程名"]:
                            msg += f"\n地点：{course['地点']}\n（物理实验室在中院4号楼，实验不同教室不同）"
                        else:
                            msg += f"\n地点：{course['地点']}"
                    return msg
                return "太棒了，没有课，睡大觉~"
            else:
                return "你还没有开启课程表功能哦，发送：/课表帮助，查看使用方法"

        def get_date_by_weekday(target_weekday):
            """
            根据指定的星期数获取当前周内该天的日期。

            参数:
            target_weekday (int): 目标星期几（1表示星期一，7表示星期日）。

            返回:
            datetime.date: 当前周内指定星期几的日期。
            """
            # 获取今天的日期
            today = datetime.date.today()

            # 获取今天是星期几 (1=星期一, ..., 7=星期日)
            current_weekday = today.isoweekday()
            # 计算日期差值
            delta_days = target_weekday - current_weekday

            # 计算目标日期
            target_date = today + datetime.timedelta(days=delta_days)

            return delta_days, target_date  # 返回 日期差 和 日期

        today = datetime.date.today()
        # 获取当前周数和星期
        msg = None
        current_count, week_day = get_today_week_day()
        if target_weekday == 0:
            if flag == 1:
                msg = get_day_course_msg(str(today), int(current_count), int(week_day))
            elif flag == 2:
                tomorrow = today + datetime.timedelta(days=1)
                if int(week_day) == 7:  # 如果今天是周末，则明天为下一个教学周的周一
                    current_count = int(current_count) + 1
                    week_day = 0
                msg = get_day_course_msg(str(tomorrow), int(current_count), int(week_day) + 1)
            elif flag == 3:
                day_after_tomorrow = today + datetime.timedelta(days=2)
                if int(week_day) == 6:  # 如果今天是周六，则后天为下一个教学周的周一
                    current_count = int(current_count) + 1
                    week_day = 0
                elif int(week_day) == 7:  # 如果今天是周末，则后天为下一个教学周的周二
                    current_count = int(current_count) + 1
                    week_day = 1
                msg = get_day_course_msg(str(day_after_tomorrow), int(current_count), int(week_day) + 2)
        else:
            delta_days, target_date = get_date_by_weekday(target_weekday)
            msg = get_day_course_msg(str(target_date), int(current_count), int(target_weekday))
        return msg

    def get_flag_week_course_msg(self, flag) -> str:
        """
        获取日课表信息
        Args:
            flag: 1-获取本周课表，2-获取下周课表

        Returns: 课表信息字符串
        """

        def get_week_course_list(course_json, w):
            week_course_dict = {}
            for course_detail in course_json["xkxx"][0].values():  # 遍历课程列表
                for time_and_place in course_detail["timeAndPlaceList"]:
                    # "classWeek": "111111111111000000000000"，其中1表示上课周，0表示本周没有这门课
                    class_week = time_and_place["classWeek"]
                    if class_week[w - 1] == '1':
                        # 获取上课时间
                        course_time = time_and_place["classSessions"]
                        # 拼接上课地点
                        campus, build = time_and_place["campusName"], time_and_place["teachingBuildingName"]
                        classroom = time_and_place["classroomName"]
                        # 整理课程信息
                        if not week_course_dict.get(time_and_place["classDay"], None):
                            week_course_dict[time_and_place["classDay"]] = []
                        week_course_dict[time_and_place["classDay"]].append({
                            "课程名": time_and_place["coureName"],
                            "教师": course_detail["attendClassTeacher"],
                            "时间": course_time,
                            "地点": ">>".join([campus, build, classroom]),
                        })
            return week_course_dict

        def get_week_course_msg(w: int):
            if w > 20:
                return f"现在是第{w}周，还没放假？还看课表？还想上课？"
            logging.info(f"获取第 {w} 周课表信息")
            if self.course_json:
                week_course_list = get_week_course_list(self.course_json, w)
                print(week_course_list)

                if len(week_course_list) > 0:
                    msg = f"第 {w} 教学周。\n一共有 {sum(len(val) for val in week_course_list.values())} 节课哦~"
                    for i in range(1, 8):
                        msg += f"\n\n星期{i}"
                        if week_course_list.get(i, None):
                            week_course_list[i].sort(key=lambda x: x["时间"])
                            for idx, course in enumerate(week_course_list[i]):
                                msg += f"\n\n第 {idx + 1} 节：{course['课程名']}"
                                msg += f"\n老师：{course['教师'].replace('*', '')}"
                                msg += f"\n时间：{self.get_course_start_end_time_string(int(course['时间']))}"
                                # 泰达>>9 为中院九号楼，西院的同学很多都不知道，因此如果出现的话单独加个备注
                                if "泰达>>9" in course["地点"]:
                                    msg += f"\n地点：{course['地点']}\n（泰达>>9 为中院九号楼，即中院图书馆！）"
                                elif "物理实验" in course["课程名"]:
                                    msg += f"\n地点：{course['地点']}\n（物理实验室在中院4号楼，实验不同教室不同）"
                                else:
                                    msg += f"\n地点：{course['地点']}"
                        else:
                            msg += "\n没有课，睡大觉~"
                    return msg
                return "震惊！一周都没有课，岂不爽歪歪？"
            else:
                return "你还没有开启课程表功能哦，发送：/课表帮助，查看使用方法"

        current_count, _ = get_today_week_day()
        if flag == 1:
            return get_week_course_msg(int(current_count))
        elif flag == 2:
            return get_week_course_msg(int(current_count) + 1)

    @staticmethod
    def get_course_start_end_time_string(idx):
        today = datetime.datetime.today()
        course_start_time = course_start_time_dict[idx]
        course_start_time = datetime.datetime.strptime(course_start_time, "%H:%M")
        course_start_time = datetime.datetime.combine(today.date(), course_start_time.time())
        course_end_time = course_start_time + datetime.timedelta(minutes=100)
        return f"{course_start_time.strftime('%H:%M')}-{course_end_time.strftime('%H:%M')}"

    @staticmethod
    def md5_string(in_str):
        md5 = hashlib.md5()
        md5.update(in_str.encode("utf-8"))
        return md5.hexdigest()


class NewEducationAdminister:
    """ 由统一身份认证登录教务系统 """

    def __init__(self, student_number, student_password):
        self.student_number = student_number
        self.student_password = student_password
        # 初始化 WebDriver，指定 chromedriver 路径
        options = Options()
        options.add_argument('--headless')  # 开启无头模式
        options.add_argument('--disable-gpu')
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-dev-shm-usage')
        remote_url = os.getenv(
            "SELENIUM_REMOTE_URL",
            # 部署方可覆盖，默认直连无需 /wd/hub 前缀
            "http://tust-selenium.matrix-studio.top:20000",
        )
        try:
            self.driver = webdriver.Remote(command_executor=remote_url, options=options)
        except Exception:
            # 兼容有些 Hub 必须带 /wd/hub 的情况
            fallback_url = remote_url.rstrip("/") + "/wd/hub"
            self.driver = webdriver.Remote(command_executor=fallback_url, options=options)

        self.driver.set_page_load_timeout(60)
        # 设置 WebDriver 显式等待
        self.wait = WebDriverWait(self.driver, 60)
        self.tmp_user_dir = os.path.join(tmp_file_dir, self.student_number)
        os.makedirs(self.tmp_user_dir, exist_ok=True)

    def login(self):
        """ 登录教务系统 """
        login_page_url = "https://id.tust.edu.cn/authserver/login?" \
                         "service=http%3A%2F%2Fjwxtxs.tust.edu.cn%3A46110%2Fj_spring_cas_security_check"
        try:
            exec_url = getattr(self.driver.command_executor, "_url", None) or str(self.driver.command_executor)
        except Exception:  # pylint: disable=broad-except
            exec_url = "unknown"
        logger.warning("[course_login] 使用 Selenium 远程地址: %s", exec_url)
        # 打开登录页面
        try:
            self.driver.get(login_page_url)
            logger.warning("[course_login] 已打开登录页面 current_url=%s", self.driver.current_url)
        except Exception as exc:  # pylint: disable=broad-except
            self.driver.quit()
            return f"登录页面加载失败：{exc}"
        # 设置浏览器全屏
        self.driver.maximize_window()

        try:
            # 等待用户名输入框加载，并输入用户名
            username_input = self.wait.until(EC.presence_of_element_located((By.ID, "username")))
            username_input.send_keys(self.student_number)
            logger.warning("[course_login] 学号已填入: %s", username_input.get_attribute("value"))

            # 等待密码输入框加载，并输入密码
            password_input = self.wait.until(EC.presence_of_element_located((By.ID, "password")))
            password_input.send_keys(self.student_password)
            logger.warning("[course_login] 密码元素获取成功，已尝试填入")

            time.sleep(2)
            # 检查是否有验证码
            captcha_img = self.driver.find_element(By.ID, "captchaImg")
            if captcha_img.is_displayed():
                logger.warning("[course_login] 验证码出现，正在处理...")

                # 定位验证码图片并截图保存
                captcha_path = "captcha.jpg"
                captcha_img.screenshot(captcha_path)
                logger.warning("[course_login] 验证码已保存到: %s", captcha_path)

                # 调用超级鹰识别验证码
                with open(captcha_path, "rb") as f:
                    # 调用超级鹰识别验证码
                    super_eagle = SuperEagleClient("Alex007", "Alex007.", "938831")
                    pic_str = super_eagle.post_pic(f.read(), 1902).get("pic_str", None)

                if pic_str:
                    logger.warning("[course_login] 验证码识别结果: %s", pic_str)
                    # 将识别结果输入到验证码框中
                    captcha_input = self.driver.find_element(By.ID, "captchaResponse")
                    captcha_input.send_keys(pic_str)
                else:
                    return "验证码识别失败，请重试！"

            # 点击登录按钮
            submit_button = None
            locators = [
                (By.ID, "login_submit"),  # 当前页面的登录按钮
                (By.ID, "submit"),        # 旧版兼容
                (By.XPATH, "//a[@id='login_submit']"),
                (By.XPATH, "//button[@type='submit']"),
            ]
            for locator in locators:
                try:
                    submit_button = self.wait.until(EC.element_to_be_clickable(locator))
                    break
                except Exception:
                    continue
            if submit_button is None:
                self._dump_page("no_submit_button")
                self.driver.quit()
                return "未找到登录按钮，页面可能有变更"

            submit_button.click()
            logger.warning("[course_login] 已点击登录按钮，等待跳转...")

            # 检查登录是否成功
            time.sleep(2)
            try:
                error_element = self.driver.find_element(By.ID, "formErrorTip")
                # formErrorTip 内部有 showErrorTip/showWarnTip
                error_text = error_element.text.strip()
                if error_element.is_displayed() and error_text:
                    logger.info("error_message: %s", error_text)
                    self.driver.quit()
                    return error_text
            except Exception:
                pass  # 如果没有错误信息，继续执行

            # 等待页面跳转完成
            self.wait.until(EC.url_contains("jwxtxs.tust.edu.cn"))
            logger.warning("[course_login] 跳转成功 current_url=%s", self.driver.current_url)
            time.sleep(2)
        except TimeoutException:
            self._dump_page("timeout_after_submit")
            self.driver.quit()
            return "登录页面加载超时或登录失败"
        except Exception as exc:  # pylint: disable=broad-except
            self._dump_page("exception_after_submit")
            self.driver.quit()
            return f"登录流程异常：{exc}"

    def get_user_course_item(self):
        try:
            # 等待 id="82020" 的 li 标签加载并点击
            li_82020 = self.wait.until(EC.element_to_be_clickable((By.ID, "82020")))
            li_82020.click()

            time.sleep(1)

            # 等待 id="82022" 的 li 标签加载并点击
            li_82022 = self.wait.until(EC.element_to_be_clickable((By.ID, "82022")))
            li_82022.click()

            time.sleep(1)

            # 等待指定 XPath 的 table 元素加载
            table_element = self.wait.until(
                EC.presence_of_element_located(
                    (By.XPATH, '/html/body/div[4]/div[2]/div[2]/div/div/div/div[3]/div[1]/div/table'))
            )

            # 获取 table 的 HTML 内容
            table_html = table_element.get_attribute("outerHTML")

            # 使用 BeautifulSoup 解析 table 内容
            soup = BeautifulSoup(table_html, "html.parser")
            rows = soup.find_all("tr")
            # 提取表头和表数据
            data = [[td.text.strip() for td in row.find_all("td")] for row in rows[1:]]
            new_data = []
            for item in data:
                if len(item) > 3:
                    new_data.append(item)
                else:
                    new_item = copy.deepcopy(new_data[-1])
                    new_item[-2] = item[-2]
                    new_item[-1] = item[-1]
                    new_data.append(new_item)
        except Exception as exc:  # pylint: disable=broad-except
            # 如果是 frp 404 或网络问题，尽量返回可读信息
            if "Not Found" in str(exc):
                raise RuntimeError("教务系统页面无法访问（可能需要校内网络/代理）") from exc
            raise
        finally:
            # 确保退出 WebDriver（忽略退出异常）
            try:
                self.driver.quit()
            except Exception:
                pass

        return new_data

    def _dump_page(self, tag: str):
        """保存当前页面源码到 tmp 目录，便于排查。"""
        try:
            timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
            filename = os.path.join(self.tmp_user_dir, f"{tag}_{timestamp}.html")
            with open(filename, "w", encoding="utf-8") as fp:
                fp.write(self.driver.page_source or "")
            logger.warning("[course_login] 页面转储到 %s", filename)
        except Exception as exc:  # pylint: disable=broad-except
            logger.warning("[course_login] 页面转储失败：%s", exc)


if __name__ == '__main__':
    user_edu_admin = EducationAdminister("23083310", "111111")
    # asyncio.run(user_edu_admin.get_user_gpa())
